package org.ashleygwinnell.snahs;

import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

import org.ashleygwinnell.snahs.RequestHandler;
import org.ashleygwinnell.snahs.ThreadedRequest;
import org.ashleygwinnell.snahs.err.RequestNotSupportedException;
import org.ashleygwinnell.snahs.err.SnahsException;

public final class SimpleHTTPServer 
{	
	/** The default port number to run the server on */
	private static final int DEFAULT_PORT = 9800;
	
	/** The default port number to run the server on */
	private static final String DEFAULT_NAME = new String("Snahs Server");
	
	/** The directory to find the server files in */
	private String FILES_DIRECTORY = new String("/");
	
	/** The server name */
	private String name;
	
	/** The author's name */
	private String author;
	
	/** the port to run on */
	private int port;
	
	/** whether or not the server is holding the connections (true = http, false = chat) */
	private boolean holdingConnections = false;
	
	/** This object should be the object that creates the SimpleHTTPServer object. */
	private Object initialisingObject;
	
	/** a list of currently help threads */
	private ArrayList<ThreadedRequest> threads = new ArrayList<ThreadedRequest>();
	
	/** Request types */
	private HashMap<String, RequestHandler> handlers = new HashMap<String, RequestHandler>();
		
	/**
	 * Creates a Simple HTTP Server with the default name and port.
	 */
	public SimpleHTTPServer()
	{
		this(DEFAULT_NAME, DEFAULT_PORT);
	}
	
	/**
	 * Create a Simple HTTP Server with the specified name.
	 * @param name The server name.
	 */
	public SimpleHTTPServer(String name)
	{
		this(name, DEFAULT_PORT);
	}
	
	/**
	 * Create a Simple HTTP Server with the specified port.
	 * @param port The server port.
	 */
	public SimpleHTTPServer(int port)
	{
		this(DEFAULT_NAME, port);
	}
	
	/**
	 * Create a Simple HTTP Server with the specified name and port.
	 * @param name The server name.
	 * @param port The server port.
	 */
	public SimpleHTTPServer(String name, int port)
	{
		this.setName(name);
		this.setPort(port);
	}

	/**
	 * Sets the name of the server
	 * @param name - the name of the server.
	 */
	public final void setName(String name)
	{
		this.name = name;
	}
	
	/**
	 * Gets the name of the server.
	 * @return The name of the server.
	 */
	public final String getName() {
		return name;
	}
	
	/**
	 * Sets the port number to run the server on.
	 * @param port The port to run the server on.
	 */
	public final void setPort(int port)
	{
		this.port = port;
	}
	
	/**
	 * Gets the port of the server.
	 * @return The server port.
	 */
	public final int getPort() 
	{
		return port;
	}
	
	
	/**
	 * Gets the number of request handlers made.
	 * @return the number of request handlers.
	 */
	public int getNumberOfRequestHandlers()
	{
		return this.handlers.size();
	}
	
	/**
	 * Sets whether the server should drop the connection after the request has been made.
	 * Set this to false for a typical http server.
	 * Set this to true for a chat server or any server where clients hold connections, such as online games, etc.
	 * The server sets this as false by default.
	 * @param b
	 */
	public void setHoldConnections(boolean b)
	{
		this.holdingConnections = b;
	}
	
	/**
	 * Returns whether the server is holding the connections with the clients or not.
	 * @see org.ashleygwinnell.snahs.SimpleHTTPServer.setHoldConnections()
	 * @return
	 */
	public boolean isHoldingConnections()
	{
		return this.holdingConnections;
	}
	
	/**
	 * Starts the webserver.
	 * @throws SnahsException 
	 */
	public final void start() throws SnahsException
	{
		ServerSocket serverSocket;
		try 
		{
			// Print out the port number for user
			serverSocket = new ServerSocket(this.getPort());
			System.out.println("---------------------------");
			System.out.println("Snahs Simple Server");
			System.out.println("Name: \t" + this.getName() + "");
			System.out.println("Author:\t" + this.getAuthor());
			System.out.println("Port:\t" + serverSocket.getLocalPort() + ".");
			System.out.println("---------------------------");
			System.out.println("Waiting for connection...\r");
			//System.out.println("---------------------------");
			
			// Server infinite loop (this blocks until it receives a request...)
			while (true) 
			{
				
				Socket socket = serverSocket.accept(); //blocking until request made.
				System.out.println("Connection Opened " + socket.getInetAddress() + ":" + socket.getPort());
				
				// Construct handler to process the request message.
				try 
				{
					ThreadedRequest request = new ThreadedRequest(socket);
					this.addThread(request);
					request.setServer(this);	
					request.start();
				}
				catch(Exception e) 
				{
				   e.printStackTrace();
				}
			}
		}
		catch (BindException be)
		{
			SnahsException e = new SnahsException();
			e.setParentException(be);
			e.setMessage("Either " + this.getName() + " is already running... ");
			e.setMessage("	...or the port (" + this.getPort() + ") is in use by another program.");
			e.setKill(true);
			throw e;
		}
		catch (IOException ioe) 
		{
			SnahsException e = new SnahsException();
			e.setParentException(ioe);
			e.setMessage(this.getName() + " experienced an IO error.");
			e.setMessage("See the stack trace for details.");
			e.setShowStackTrace(true);
			e.setKill(true);
			throw e;
		}
	}
	
	/**
	 * Adds a thread to the currently held threads list.
	 * @param r
	 */
	private void addThread(ThreadedRequest r)
	{
		this.threads.add(r);
	}
	
	/**
	 * Sets the directory to find the files in.
	 * @param dir - The directory to find the files in.
	 * @throws SnahsException 
	 */
	public final void setFilesDirectory(String dir) throws SnahsException
	{
		File file = new File(dir);
		if (!file.exists())
		{
			SnahsException e = new SnahsException();
			e.setMessage("The files directory input '" + dir + "' doesn't exist.");
			e.setMessage("Absolute path: " + file.getAbsolutePath());
			e.setKill(true);
			throw e;
		}
		if (!file.isDirectory())
		{
			SnahsException e = new SnahsException();
			e.setMessage("The files directory input '" + dir + "' is not a valid directory.");
			e.setMessage("Absolute path: " + file.getAbsolutePath());
			e.setKill(true);
			throw e;
		}
		
		FILES_DIRECTORY = dir;
	} 
	
	/**
	 * Gets the files directory.
	 * @return - The files directory.
	 */
	public final String getFilesDirectory()
	{
		return FILES_DIRECTORY;
	}

	public final void setAuthor(String a) 
	{
		this.author = a;
	}
	
	public final String getAuthor() {
		return author;
	}
	
	public ArrayList<ThreadedRequest> getThreads() {
		return threads;
	}
	
	/**
	 * Sets the initialising object / class.
	 * The object passed into this method should be the object that creates the SimpleHTTPServer object.
	 * @param o
	 */
	public void setInitialiingObject(Object o)
	{
		this.initialisingObject = o;
	}
	
	/**
	 * Returns the Object passed in with setInitialisingObject().
	 * You will need to cast the Object to it's former type. For example: 
	 * 		If the initialising Object's name is "Chat".
	 * 		((Chat) this.getInitialisingObject()).someExampleMethod();
	 * @return
	 */
	public Object getInitialisingObject()
	{
		return this.initialisingObject;
	}
	
	public final RequestHandler getHandler(String key) throws RequestNotSupportedException
	{
		RequestHandler h = this.handlers.get(key);
		if (h == null)
		{
			throw new RequestNotSupportedException(key);
		}
		return h;
	}
	
	public final void addHandler(RequestHandler handler)
	{
		this.handlers.put(handler.getType(), handler);
	}
	
	
	
	
}